package services

import (
	"bytes"
	"context"
	"gitee.com/igolang/imoney/internal/app/system/entities"
	"gitee.com/igolang/imoney/internal/app/system/entities/constants"
	"gitee.com/igolang/imoney/internal/app/system/services/dict"
	"gitee.com/igolang/imoney/internal/app/system/tools"
	commonEntities "gitee.com/igolang/imoney/internal/common/entities"
	"gitee.com/igolang/imoney/internal/common/httpResp"
	commonTools "gitee.com/igolang/imoney/internal/common/tools"
	"gitee.com/igolang/imoney/internal/core/client"
	"gitee.com/igolang/imoney/internal/core/logger"
	"gitee.com/igolang/imoney/internal/models/dao"
	"gitee.com/igolang/imoney/internal/models/model"
	"gitee.com/igolang/pkg/xgorm"
	"gitee.com/igolang/pkg/xtime"
	"gitee.com/igolang/pkg/xutils"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"gorm.io/gen"
	"net/http"
	"slices"
	"time"
)

var Role = &role{}

type role struct{}

func (s *role) List(ctx *gin.Context) (*commonEntities.ListResp, error) {
	var req entities.RolePageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	req.Page()
	conds := s.formatListConds(req)
	total, err := dao.SysRole.Where(conds...).Count()
	if err != nil {
		return nil, err
	}
	result := &commonEntities.ListResp{
		Total: total,
		Rows:  make([]*model.SysRole, 0),
	}
	if total == 0 {
		return result, err
	}
	rows, err := dao.SysRole.Where(conds...).Order(dao.SysRole.RoleSort).Offset(req.Offset).Limit(req.PageSize).Rows()
	if err != nil {
		return nil, err
	}
	dc := client.DB()
	data := make([]map[string]any, 0)
	for rows.Next() {
		row := &model.SysRole{}
		if dc.ScanRows(rows, row) == nil {
			item := xgorm.ModelToMapWithTag(row, "json")
			item["admin"] = row.RoleKey == constants.SuperAdmin
			item["menuCheckStrictly"] = cast.ToBool(row.MenuCheckStrictly)
			item["deptCheckStrictly"] = cast.ToBool(row.DeptCheckStrictly)
			data = append(data, item)
		}
	}
	result.Rows = data
	return result, nil
}

func (s *role) DeptTree(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	info, err := dao.SysRole.Where(dao.SysRole.RoleID.Eq(id)).Take()
	if err != nil {
		return nil, err
	}
	conds := []gen.Condition{
		dao.SysRoleDept.RoleID.Eq(id),
	}
	if info.DeptCheckStrictly == 1 {
		conds = append(conds, dao.SysDept.Columns(dao.SysDept.DeptID).NotIn(
			dao.SysDept.Select(dao.SysDept.ParentID).Join(dao.SysRoleDept, dao.SysRoleDept.DeptID.EqCol(dao.SysDept.DeptID)).Where(dao.SysRoleDept.RoleID.Eq(id))))
	}

	rows, err := dao.SysDept.Select(dao.SysDept.DeptID).LeftJoin(dao.SysRoleDept, dao.SysRoleDept.DeptID.EqCol(dao.SysDept.DeptID)).Where(conds...).Find()
	if err != nil {
		return nil, err
	}
	ids := make([]int64, 0)
	for _, v := range rows {
		ids = append(ids, v.DeptID)
	}
	treeList, err := Dept.Tree("")
	if err != nil {
		return nil, err
	}

	return map[string]any{
		"checkedKeys": ids,
		"depts":       treeList,
	}, nil
}

func (s *role) Detail(ctx *gin.Context) (map[string]any, error) {
	id := cast.ToInt64(ctx.Param("id"))
	if id == 0 {
		return nil, httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	row, err := dao.SysRole.Where(dao.SysRole.RoleID.Eq(id)).Take()
	if err != nil {
		return nil, err
	}
	data := xgorm.ModelToMapWithTag(row, "json")
	data["admin"] = row.RoleKey == constants.SuperAdmin
	data["menuCheckStrictly"] = cast.ToBool(row.MenuCheckStrictly)
	data["deptCheckStrictly"] = cast.ToBool(row.DeptCheckStrictly)
	return data, nil
}

func (s *role) Save(ctx *gin.Context) (err error) {
	var req entities.RoleSaveReq
	if err = ctx.ShouldBindJSON(&req); err != nil {
		return
	}
	if (ctx.Request.Method == http.MethodPut && req.RoleId == 0) || req.RoleName == "" || req.RoleKey == "" || req.RoleSort == nil {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}
	if req.RoleId == constants.SuperAdminId {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	cond := make([]gen.Condition, 0)
	index := 0
	if req.RoleId > 0 {
		cond = append(cond, dao.SysRole.RoleID.Neq(req.RoleId))
		index = 1
	}
	cond = append(cond, dao.SysRole.RoleName.Eq(req.RoleName))
	count, _ := dao.SysRole.Where(cond...).Count()
	if count > 0 {
		return errors.Errorf("保存角色'%s'失败，角色名称已存在", req.RoleName)
	}
	cond[index] = dao.SysRole.RoleKey.Eq(req.RoleKey)
	count, _ = dao.SysRole.Where(cond...).Count()
	if count > 0 {
		return errors.Errorf("保存角色'%s'失败，角色权限已存在", req.RoleName)
	}

	row := &model.SysRole{}
	if req.RoleId > 0 {
		row, err = dao.SysRole.Where(dao.SysRole.RoleID.Eq(req.RoleId)).Take()
		if err != nil {
			return
		}
	}
	if req.Status == "" {
		req.Status = "0"
	}

	userInfo := tools.GetUserInfo(ctx)
	row.RoleKey = req.RoleKey
	row.RoleName = req.RoleName
	row.RoleSort = *req.RoleSort
	row.DeptCheckStrictly = cast.ToInt64(req.DeptCheckStrictly)
	row.MenuCheckStrictly = cast.ToInt64(req.MenuCheckStrictly)
	row.Remark = req.Remark
	row.Status = req.Status
	row.UpdateTime = xtime.Time(time.Now())
	row.UpdateBy = userInfo.User.UserName
	if req.RoleId == 0 {
		row.CreateBy = userInfo.User.UserName
		row.CreateTime = xtime.Time(time.Now())
		return dao.Q.Transaction(func(tx *dao.Query) error {
			var e error
			e = tx.SysRole.Create(row)
			if e != nil {
				return e
			}
			return s.setRoleRelation(tx, row.RoleID, req, "menu")
		})
	}
	// 更新
	data := xgorm.ModelToMap(row)
	err = dao.Q.Transaction(func(tx *dao.Query) error {
		var e error
		_, e = tx.SysRole.Where(tx.SysRole.RoleID.Eq(row.RoleID)).Updates(data)
		if e != nil {
			return e
		}
		return s.setRoleRelation(tx, row.RoleID, req, "menu")
	})
	if err != nil {
		return
	}
	s.updateUserCache(req.RoleId)
	return
}

func (s *role) DataScope(ctx *gin.Context) (err error) {
	var req entities.RoleSaveReq
	if err = ctx.ShouldBindJSON(&req); err != nil {
		return
	}
	if req.RoleId == 0 || req.DataScope == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	if req.DataScope != "2" && len(req.DeptIds) > 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	defer s.updateUserCache(req.RoleId)
	return dao.Q.Transaction(func(tx *dao.Query) error {
		_, e := tx.SysRole.Where(tx.SysRole.RoleID.Eq(req.RoleId)).Updates(&model.SysRole{
			DataScope:         req.DataScope,
			DeptCheckStrictly: cast.ToInt64(req.DeptCheckStrictly)})
		if e != nil {
			return e
		}
		return s.setRoleRelation(tx, req.RoleId, req, "dept")
	})
}

func (s *role) ChangeStatus(ctx *gin.Context) (err error) {
	var req struct {
		RoleId int64  `json:"roleId"`
		Status string `json:"status"`
	}
	if err = ctx.ShouldBindJSON(&req); err != nil {
		return err
	}
	if req.RoleId == 0 || req.Status == "" {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	if req.RoleId == constants.SuperAdminId {
		return httpResp.ErrorCode(http.StatusForbidden)
	}

	_, err = dao.SysRole.Where(dao.SysRole.RoleID.Eq(req.RoleId)).Update(dao.SysRole.Status, req.Status)
	s.updateUserCache(req.RoleId)
	return err
}

func (s *role) updateUserCache(roleId int64) {
	// 更新用户信息
	xutils.RecoverGo(func() {
		rows, _ := dao.SysUserRole.Where(dao.SysUserRole.RoleID.Eq(roleId)).Find()
		if len(rows) == 0 {
			return
		}

		for _, v := range rows {
			logger.Log().Debug("更新用户缓存", "uid", v, "err", User.LoginUserClean(v.UserID))
		}
	}, commonTools.ErrorHandler(context.Background(), "编辑角色，更新用户缓存"))
}

func (s *role) setRoleRelation(tx *dao.Query, roleId int64, req entities.RoleSaveReq, t string) error {
	if roleId == 0 {
		return errors.New("请指定角色")
	}
	var err error
	if t == "menu" {
		if _, err = tx.SysRoleMenu.Where(tx.SysRoleMenu.RoleID.Eq(roleId)).Delete(); err != nil {
			return err
		}
		if len(req.MenuIds) == 0 {
			return nil
		}

		data := make([]*model.SysRoleMenu, 0)
		for _, v := range req.MenuIds {
			data = append(data, &model.SysRoleMenu{
				RoleID: roleId,
				MenuID: v,
			})
		}

		if err = tx.SysRoleMenu.CreateInBatches(data, 100); err != nil {
			return err
		}

	}

	if t == "dept" {
		if _, err = tx.SysRoleDept.Where(tx.SysRoleDept.RoleID.Eq(roleId)).Delete(); err != nil {
			return err
		}
		if len(req.DeptIds) == 0 {
			return nil
		}
		data := make([]*model.SysRoleDept, 0)
		for _, v := range req.DeptIds {
			data = append(data, &model.SysRoleDept{
				RoleID: roleId,
				DeptID: v,
			})
		}
		if err = tx.SysRoleDept.CreateInBatches(data, 100); err != nil {
			return err
		}
	}

	return err
}

func (s *role) Delete(ctx *gin.Context) error {
	ids := commonTools.String2Int64s(ctx.Param("ids"))
	if len(ids) == 0 {
		return httpResp.ErrorCode(httpResp.ErrCodeInvalidParams)
	}

	if slices.Contains(ids, constants.SuperAdminId) {
		return httpResp.ErrorCode(http.StatusForbidden)
	}
	// 校验
	type roleCnt struct {
		RoleID   int64  `json:"roleID"`
		RoleKey  string `json:"roleKey"`
		RoleName string `json:"roleName"`
		Cnt      int    `json:"cnt"`
	}
	var rows []roleCnt

	err := dao.SysRole.Select(
		dao.SysRole.RoleID,
		dao.SysRole.RoleName,
		dao.SysRole.RoleKey,
		dao.SysUserRole.RoleID.Count().As("Cnt"),
	).Where(dao.SysRole.RoleID.In(ids...)).LeftJoin(
		dao.SysUserRole,
		dao.SysUserRole.RoleID.EqCol(dao.SysRole.RoleID)).Scan(&rows)
	if err != nil {
		return err
	}

	for _, row := range rows {
		if row.Cnt > 0 || row.RoleKey == constants.SuperAdmin || row.RoleID == constants.SuperAdminId {
			return httpResp.ErrWarning("%s已分配,不能删除", row.RoleName)
		}
	}

	err = dao.Q.Transaction(func(tx *dao.Query) error {
		var e error
		if _, e = tx.SysRole.Where(tx.SysRole.RoleID.In(ids...)).Delete(); e != nil {
			return e
		}
		// 删除对应的菜单
		if _, e = tx.SysRoleMenu.Where(tx.SysRoleMenu.RoleID.In(ids...)).Delete(); e != nil {
			return e
		}

		if _, e = tx.SysRoleDept.Where(tx.SysRoleDept.RoleID.In(ids...)).Delete(); e != nil {
			return e
		}

		return nil
	})

	return err
}

func (s *role) Export(ctx *gin.Context) (*bytes.Buffer, error) {
	var req entities.RolePageListReq
	if err := ctx.ShouldBind(&req); err != nil {
		return nil, err
	}
	req.Page()
	conds := s.formatListConds(req)
	rows, err := dao.SysRole.Where(conds...).Order(dao.SysRole.RoleSort).Rows()
	if err != nil {
		return nil, err
	}
	dc := client.DB()
	header := []string{"角色序号", "角色名称", "角色权限", "角色排序", "数据范围", "角色状态"}
	data := [][]string{header}
	commonStatusOptions, _ := dict.Get("sys_normal_disable")
	dataScopeOptions, _ := dict.Get("sys_data_scope")
	for rows.Next() {
		row := &model.SysRole{}
		if dc.ScanRows(rows, row) == nil {
			data = append(data, []string{
				cast.ToString(row.RoleID),
				row.RoleName,
				row.RoleKey,
				cast.ToString(row.RoleSort),
				dict.LabelOptions(dataScopeOptions, row.DataScope),
				dict.LabelOptions(commonStatusOptions, row.Status),
			})
		}
	}
	fh := xutils.CreateExcelFileWithData(data)
	defer fh.Close()
	return fh.WriteToBuffer()
}

func (s *role) formatListConds(req entities.RolePageListReq) []gen.Condition {
	conds := []gen.Condition{dao.SysRole.RoleID.Neq(constants.SuperAdminId)}
	if req.RoleName != "" {
		conds = append(conds, dao.SysRole.RoleName.Like(`%`+req.RoleName+`%`))
	}
	if req.RoleKey != "" {
		conds = append(conds, dao.SysRole.RoleKey.Like(`%`+req.RoleKey+`%`))
	}
	if req.Status != "" {
		conds = append(conds, dao.SysRole.Status.Eq(req.Status))
	}
	if !req.BT.IsZero() && !req.ET.IsZero() {
		conds = append(conds, dao.SysRole.CreateTime.Gte(req.BT))
		conds = append(conds, dao.SysRole.CreateTime.Lte(req.ET))
	}

	return conds
}
